﻿using System;
using System.Linq.Expressions;
using System.Windows;
using nRoute.Utilities;
using System.Reflection;
using System.Windows.Input;
using System.ComponentModel;
#if (WINRT)
using Windows.UI.Xaml.Input;
#endif

namespace nRoute.Components
{
    public abstract class CommandBase<T>
        : IActionCommand
    {
        protected const string ERROR_EXPECTED_TYPE = "Expected parameter for command ({0}) must be of {1} type";

        private bool _isActive = true;

        public CommandBase() { }

        public CommandBase(bool isActive) : this()
        {
            _isActive = isActive;
        }

#region Additional Methods

        public virtual bool CanExecute(T parameter)
        {
            return IsActive && OnCanExecute(parameter); 
        }

        public virtual void Execute(T parameter)
        {
            if (CanExecute(parameter))
            {
                OnExecute(parameter);
                OnCommandExecuted(new CommandEventArgs(parameter));
            }
        }

        protected virtual void OnRequeryCanExecute()
        {
            if (CanExecuteChanged != null) CanExecuteChanged(this, System.EventArgs.Empty);
        }

#endregion

#region Abstract Methods

        protected abstract bool OnCanExecute(T parameter);

        protected abstract void OnExecute(T parameter);

#endregion

#region IActionCommand Members

        public bool IsActive
        {
            get
            {
                return _isActive;
            }
            set
            {
                if (_isActive != value)
                {
                    _isActive = value;
                    PropertyChanged.Notify(() => IsActive);
                    RequeryCanExecute();
                }
            }
        }

        public void RequeryCanExecute()
        {
            OnRequeryCanExecute();
        }

#endregion

#region IReverseCommand Members

        public event System.EventHandler<CommandEventArgs> CommandExecuted;

#endregion

#region ICommand Members

        public event EventHandler CanExecuteChanged;

        bool ICommand.CanExecute(object parameter)
        {
            CheckParameterType(parameter);
            return CanExecute(ParseParameter(parameter, typeof(T)));
        }

        void ICommand.Execute(object parameter)
        {
            CheckParameterType(parameter);
            Execute(ParseParameter(parameter, typeof(T)));
        }

#endregion

#region IWeakEventListener Members

        bool IWeakEventListener.ReceiveWeakEvent(System.Type managerType, object sender, EventArgs e)
        {
            RequeryCanExecute();
            return true;        // as in always listening
        }

#endregion

#region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string propertyName)
        {
            Guard.ArgumentNotNullOrWhiteSpace(propertyName, "propertyName");
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void NotifyPropertyChanged<P>(Expression<System.Func<P>> propertySelector)
        {
            Guard.ArgumentNotNull(propertySelector, "propertySelector");
            PropertyChanged.Notify<P>(propertySelector);
        }

#endregion

#region Helpers

        protected void OnCommandExecuted(CommandEventArgs args)
        {
            if (CommandExecuted != null) CommandExecuted(this, args);
        }

        protected virtual T ParseParameter(object parameter, System.Type parseAsType)
        {
            if (parameter == null) return default(T);
            
#if (WINRT)
            if (parseAsType.GetTypeInfo().IsEnum)
#else
            if (parseAsType.IsEnum)
#endif
            {
                return (T)System.Enum.Parse(parseAsType, System.Convert.ToString(parameter), true);
            }
#if (WINRT)
            else if (parseAsType.GetTypeInfo().IsValueType)
#else
            else if (parseAsType.IsValueType)
#endif
            {
                return (T)System.Convert.ChangeType(parameter, parseAsType, null);
            }
            else
            {
                return (T)parameter;
            }
        }

        protected void CheckParameterType(object parameter)
        {
            if (parameter == null) return;
#if (WINRT)
            var _typeInfo = typeof(T).GetTypeInfo();
            if (_typeInfo.IsValueType) return;
            Guard.ArgumentValue((!_typeInfo.IsAssignableFrom(parameter.GetType().GetTypeInfo())), "parameter", ERROR_EXPECTED_TYPE,
                this.GetType().FullName, typeof(T).FullName);
#else
            if (typeof(T).IsValueType) return;
            Guard.ArgumentValue((!typeof(T).IsAssignableFrom(parameter.GetType())), "parameter", ERROR_EXPECTED_TYPE, 
                this.GetType().FullName, typeof(T).FullName);
#endif
        }

#endregion

    }
}
